Skip to content

fix: pin GitHub Actions to commit SHAs - #5

Merged
SafeEval merged 1 commit into
mainfrom
agent/pin-actions
Jul 30, 2026
Merged

fix: pin GitHub Actions to commit SHAs#5
SafeEval merged 1 commit into
mainfrom
agent/pin-actions

Conversation

@SafeEval

Copy link
Copy Markdown
Member

Every uses: in this repo referenced a mutable tag. I'd recommended SHA pinning in the sibling repo's README but never checked whether these workflows actually did it — they didn't.

Why it matters here specifically

release.yml runs with contents: write — enough to push tags and publish releases. A tag like actions/checkout@v4 can be repointed by whoever controls that repo, so a moved or compromised tag would execute new code holding that token, with no change to anything in this repository. Tag pinning is a trust-on-every-run assumption; SHA pinning is trust-once.

What changed

Pinned to the commits @v4 and @v5 resolve to today, so runtime behavior is unchanged and only the mutability is removed — no version bump smuggled in:

Action Was Now
actions/checkout @v4 @11d5960a326750d5838078e36cf38b85af677262 (v4.4.0)
actions/setup-go @v5 @40f1582b2485089dde7abd97c1529aa768e1baff (v5.6.0)

All five call sites across ci.yml and release.yml. Each SHA was verified against upstream git ls-remote to confirm it's a real commit and matches the version in its trailing comment.

Note upstream is now at v7 for both. I deliberately did not upgrade — that's a separate change with its own testing burden, and mixing it into a security fix would obscure what actually changed.

Regression guard

A new CI step fails on any uses: not pinned to a full 40-character SHA.

Worth flagging: my first version of this guard had a false positive — it matched the word uses: inside its own grep pattern and comment, so it failed on a clean tree. It's now anchored to uses: as a YAML step key. Tested both directions: passes when clean, and catches @v4, @main, abbreviated SHAs, and @v1.2.3.

It also avoids grep -P, since PCRE support isn't universal across greps.

Verification

  • All 5 pins resolved and verified against upstream
  • Guard tested positively and negatively (4 injection cases)
  • Both workflows parse as YAML; every run block passes bash -n
  • gofmt, go vet, go test -race ./... clean

Note on the released tag

v1.1.0 was published with unpinned actions. Nothing is known to be wrong with it — the released binaries are unaffected, since this only concerns how CI builds them — but the next release will carry the pins.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VSrrciEDBTuFNMtKocScML

Every `uses:` referenced a mutable tag. A tag can be repointed by whoever
controls the upstream repo, and these workflows run with a token that has
`contents: write` — enough to push tags and publish releases. A moved tag
would execute new code with that token and nothing in the repo would change.

Pin to the commits `@v4` and `@v5` resolve to today, so behavior is
identical and only the mutability is removed:

  actions/checkout  11d5960 (v4.4.0)
  actions/setup-go  40f1582 (v5.6.0)

Add a CI step that fails on any `uses:` not pinned to a full 40-character
SHA, so this cannot regress. It anchors on `uses:` as a YAML step key
rather than matching the token anywhere, since an earlier version flagged
its own grep pattern; verified it passes when clean and catches tags,
@main, and abbreviated SHAs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VSrrciEDBTuFNMtKocScML
@SafeEval
SafeEval marked this pull request as ready for review July 30, 2026 17:37
@SafeEval
SafeEval merged commit a1579bf into main Jul 30, 2026
1 check passed
@SafeEval
SafeEval deleted the agent/pin-actions branch July 30, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant